package cn.skyquant.quant4j.service.boot.web.controller;

import cn.skyquant.quant4j.api.authority.UserDTO;
import cn.skyquant.quant4j.api.dto.ResultDTO;
import cn.skyquant.quant4j.api.quant.TimeBar;
import cn.skyquant.quant4j.api.quant.jforex.JForexService;
import cn.skyquant.quant4j.api.strategy.*;
import cn.skyquant.quant4j.sdk.enums.TradeDirection;
import cn.skyquant.quant4j.sdk.util.bean.BeanTools;
import cn.skyquant.quant4j.sdk.util.time.CalendarUtils;
import cn.skyquant.quant4j.sdk.util.time.Period;
import cn.skyquant.quant4j.service.boot.model.authority.AppContext;
import cn.skyquant.quant4j.service.boot.model.authority.RoleAuthority;
import cn.skyquant.quant4j.service.boot.model.strategy.*;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

@RestController
@RequestMapping("/strategy_test")
@RoleAuthority(roleNames = {RoleAuthority.administrator, RoleAuthority.client})
public class StrategyTestController {
    @Resource
    StrategyTestService strategyTestService;
    @Resource
    StrategyRepository strategyRepository;
    @Resource
    StrategyTestRepository strategyTestRepository;
    @Resource
    StrategyTestDailyRepository strategyTestDailyRepository;
    @Resource
    StrategyTestStatisticsRepository strategyTestStatisticsRepository;
    @Resource
    StrategyTestOpenRepository strategyTestOpenRepository;
    @Resource
    StrategyTestCloseRepository strategyTestCloseRepository;
    @Autowired
    JForexService jForexService;
    @Resource
    AppContext appContext;

    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    @ResponseBody
    public ResultDTO<StrategyTestDTO> get(@PathVariable(required = true) int id) {
        Optional<StrategyTest> strategyTestOptional = strategyTestRepository.findById(id);
        if (strategyTestOptional.isPresent()) {
            StrategyTest strategyTest = strategyTestOptional.get();
            return ResultDTO.success(converDoToDto(strategyTest,true));
        }
        return ResultDTO.error2;
    }

    @RequestMapping(value = "/{id}/symbols", method = RequestMethod.GET)
    @ResponseBody
    public ResultDTO<List<String>> symbols(@PathVariable(required = true) int id) {
        Optional<StrategyTest> strategyTestOptional = strategyTestRepository.findById(id);
        if (!strategyTestOptional.isPresent()) {
            return ResultDTO.error2;
        }
        Set<String> symbolSet = new HashSet<>();
        List<StrategyTestOpen> openList = strategyTestOpenRepository.findByTestId(id);
        for(StrategyTestOpen open:openList){
            symbolSet.add(open.symbol);
        }
        return ResultDTO.success(symbolSet.stream().sorted(Comparator.comparing(String::toString)).collect(Collectors.toList()));
    }

    private static List<Object[]> objects = new ArrayList<>();
    @RequestMapping(value = "/{id}/chart", method = RequestMethod.GET)
    @ResponseBody
    public ResultDTO<Map> chart(@PathVariable(required = true) int id) {
        Optional<StrategyTest> strategyTestOptional = strategyTestRepository.findById(id);
        if (!strategyTestOptional.isPresent()) {
            return ResultDTO.error2;
        }
        Map<String,Object> resultMap =Maps.newHashMap();
        List<Map> listMap = new ArrayList<>();
        resultMap.put("list",listMap);
        Set<String> symbolSet = new HashSet<>();
        Set<String> dates = new HashSet<>();
        StrategyTest strategyTest = strategyTestOptional.get();
        List<StrategyTestOpen> openList = strategyTestOpenRepository.findByTestId(id);
        List<StrategyTestClose> closeList =  strategyTestCloseRepository.findByTestId(id);
        List<StrategyTestDaily> dailyList = strategyTestDailyRepository.findByTestId(id);

        Map<String,List<Object[]>> buyOpenMap = Maps.newHashMap();
        Map<String,List<Object[]>> sellOpenMap = Maps.newHashMap();
        Map<String,List<Object[]>> buyTpCloseMap = Maps.newHashMap();
        Map<String,List<Object[]>> buySlCloseMap = Maps.newHashMap();
        Map<String,List<Object[]>> sellTpCloseMap = Maps.newHashMap();
        Map<String,List<Object[]>> sellSlCloseMap = Maps.newHashMap();

        List<Object[]> dailyObj = new ArrayList<>();
        for(StrategyTestDaily daily:dailyList){
            dailyObj.add(new Object[]{CalendarUtils.formatGMTSimple(daily.thedate.getTime()),daily.open,daily.close,daily.high,daily.low});
        }

        for(StrategyTestOpen open:openList){
            symbolSet.add(open.symbol);
            if("BUY".equalsIgnoreCase(open.action)){
                List<Object[]> opens = buyOpenMap.get(open.symbol);
                if(opens==null){
                    opens = Lists.newArrayList();
                    buyOpenMap.put(open.symbol,opens);
                }
                opens.add(new Object[]{CalendarUtils.formatGMTSimple(open.thedate),open.openPrice,open.amount,open.tp,open.sl});
            }else if("SELL".equalsIgnoreCase(open.action)){
                List<Object[]> opens = sellOpenMap.get(open.symbol);
                if(opens==null){
                    opens = Lists.newArrayList();
                    sellOpenMap.put(open.symbol,opens);
                }
                opens.add(new Object[]{CalendarUtils.formatGMTSimple(open.thedate),open.openPrice,open.amount,open.tp,open.sl});
            }
        }
        for(StrategyTestClose close:closeList){
            symbolSet.add(close.symbol);
            if("BUY".equalsIgnoreCase(close.action)){
                if(close.profit>=0){
                    List<Object[]> closeTp = buyTpCloseMap.get(close.symbol);
                    if(closeTp==null){
                        closeTp = Lists.newArrayList();
                        buyTpCloseMap.put(close.symbol,closeTp);
                    }
                    closeTp.add(new Object[]{CalendarUtils.formatGMTSimple(close.thedate),close.closePrice,close.amount,close.profit});
                }else{
                    List<Object[]> closeSl = buySlCloseMap.get(close.symbol);
                    if(closeSl==null){
                        closeSl = Lists.newArrayList();
                        buySlCloseMap.put(close.symbol,closeSl);
                    }
                    closeSl.add(new Object[]{CalendarUtils.formatGMTSimple(close.thedate),close.closePrice,close.amount,close.profit});
                }

            }else if("SELL".equalsIgnoreCase(close.action)){

                if(close.profit>=0){
                    List<Object[]> closeTp = sellTpCloseMap.get(close.symbol);
                    if(closeTp==null){
                        closeTp = Lists.newArrayList();
                        sellTpCloseMap.put(close.symbol,closeTp);
                    }
                    closeTp.add(new Object[]{CalendarUtils.formatGMTSimple(close.thedate),close.closePrice,close.amount,close.profit});
                }else{
                    List<Object[]> closeSL = sellSlCloseMap.get(close.symbol);
                    if(closeSL==null){
                        closeSL = Lists.newArrayList();
                        sellSlCloseMap.put(close.symbol,closeSL);
                    }
                    closeSL.add(new Object[]{CalendarUtils.formatGMTSimple(close.thedate),close.closePrice,close.amount,close.profit});
                }

            }
        }
        String from = CalendarUtils.formatGMTStandard(strategyTest.beginDate.getTime());
        String to = CalendarUtils.formatGMTStandard(strategyTest.endDate.getTime()+86400000);
        for(String symbol:symbolSet){
            Map<String,Object> map = new HashMap<>();
            listMap.add(map);
            map.put("symbol",symbol);
            ResultDTO<List<TimeBar>> barListResult = jForexService.getBar(symbol, Period.daily, TradeDirection.LONG,from,to);
            if(!barListResult.okData()){
                continue;
            }
            List<TimeBar> barList = barListResult.data;

            List<Object[]> barObjs = Lists.newArrayList();
            for(TimeBar bar:barList){
                String thedate = CalendarUtils.formatGMTSimple(bar.time);
                dates.add(thedate);
                barObjs.add(bar.getObj());
            }
            map.put("bars",barObjs);
            if(buyOpenMap.containsKey(symbol)){
                map.put("buyopen",buyOpenMap.get(symbol));
            }else{
                map.put("buyopen",objects);
            }
            if(sellOpenMap.containsKey(symbol)){
                map.put("sellopen",sellOpenMap.get(symbol));
            }else{
                map.put("sellopen",objects);
            }
            if(buyTpCloseMap.containsKey(symbol)){
                map.put("buytpclose",buyTpCloseMap.get(symbol));
            }else{
                map.put("buytpclose",objects);
            }
            if(buySlCloseMap.containsKey(symbol)){
                map.put("buyslclose",buySlCloseMap.get(symbol));
            }else{
                map.put("buyslclose",objects);
            }
            if(sellTpCloseMap.containsKey(symbol)){
                map.put("selltpclose",sellTpCloseMap.get(symbol));
            }else{
                map.put("selltpclose",objects);
            }
            if(sellSlCloseMap.containsKey(symbol)){
                map.put("sellslclose",sellSlCloseMap.get(symbol));
            }else{
                map.put("sellslclose",objects);
            }
        }
        List<String> dateList = dates.stream().sorted(Comparator.comparing(String::toString)).collect(Collectors.toList());
        resultMap.put("dates",dateList);
        resultMap.put("dailyObj",dailyObj);
        resultMap.put("symbols",symbolSet.stream().sorted(Comparator.comparing(String::toString)).collect(Collectors.toList()));
        return ResultDTO.success(resultMap);
    }

    @RequestMapping(value = "/compare", method = RequestMethod.GET)
    @ResponseBody
    public ResultDTO<Map> compare(@RequestParam(required = true,name = "ids") String ids) {
        String[] idarr= ids.split("_");
        if(idarr.length<=0 || idarr.length>5){
            return ResultDTO.error("比对的测试数据最少0个,最多5个,请检查参数");
        }
        Set<String> dates = Sets.newHashSet();
        List<Map<String,Object>> nameList = Lists.newArrayList();
        List<Map<String,Object>> listMap = Lists.newArrayList();
        Map<String,Object> resultMap = new HashMap<>();
        resultMap.put("nameList",nameList);
        resultMap.put("dataList",listMap);
        Set<Integer> levelSet = new HashSet<>();
        List<StrategyTestStatisticsMap> stsmList = Lists.newArrayList();
        for(String id:idarr){
            if(id==null || id.trim().length()<=0){
                continue;
            }
            int idn = Integer.parseInt(id);
            Optional<StrategyTest> strategyTestOptional = strategyTestRepository.findById(idn);
            if (strategyTestOptional.isPresent()) {
                StrategyTest strategyTest = strategyTestOptional.get();
                Map<String,Object> kv = new HashMap<>();
                kv.put("id",idn);
                kv.put("name",strategyTest.name);
                nameList.add(kv);
                List<StrategyTestDaily> dailyList = strategyTestDailyRepository.findByTestId(idn);
                List<StrategyTestStatistics> statisticsList = strategyTestStatisticsRepository.findByTestId(idn);
                List<Object[]> list= new ArrayList<>();
                Map<String,Object> dv = new HashMap<>();
                for(StrategyTestDaily st:dailyList){
                    String thedate = CalendarUtils.formatSimple(st.thedate);
                    dates.add(CalendarUtils.formatSimple(st.thedate));
                    list.add(new Object[]{thedate,st.equity});
                }
                StrategyTestStatisticsMap sttm = new StrategyTestStatisticsMap(idn,strategyTest.name);
                for(StrategyTestStatistics sts : statisticsList){
                    levelSet.add(sts.tradeLevel);
                    sttm.put(sts.tradeLevel,sts.profit,sts.beginTime.getTime(),sts.endTime.getTime(),sts.amount,sts.distance);
                }
                dv.put("id",idn);
                dv.put("name",strategyTest.name);
                dv.put("data",list);
                stsmList.add(sttm);
                listMap.add(dv);
            }
        }
        List<Integer> statisticsLabel = levelSet.stream().sorted(Comparator.comparing(Integer::intValue)).collect(Collectors.toList());
        Map<Integer,Integer> indexMap = Maps.newHashMap();
        for(int i=0;i<statisticsLabel.size();i++){
            indexMap.put(statisticsLabel.get(i),i);
        }
        stsmList.forEach(x->x.init(indexMap));
        resultMap.put("statLabel",statisticsLabel);
        resultMap.put("stsm",stsmList);
        resultMap.put("dates",dates.stream().sorted(Comparator.comparing(String::toString)).collect(Collectors.toList()));
        return ResultDTO.success(resultMap);
    }


    //找到指定时间所有的压力位和支撑位 app端使用
    @RequestMapping(value = "/list", method = RequestMethod.GET)
    @ResponseBody
    public ResultDTO<Page<StrategyTestDTO>> findByPage(
            @RequestParam(value = "strategyName", required = false) String strategyName,
            @PageableDefault(value = 15, sort = {"startTime"}, direction = Sort.Direction.DESC) Pageable pageable) {
        if (strategyName == null) {
            Page<StrategyTest> page = strategyTestRepository.findAll(pageable);
            return ResultDTO.success(converDoToDtoPage(page));
        } else {
            Page<StrategyTest> page = strategyTestRepository.findByStrategyName(strategyName, pageable);
            return ResultDTO.success(converDoToDtoPage(page));
        }
    }

    @RequestMapping(value = "/start", method = RequestMethod.POST)
    @ResponseBody
    public ResultDTO<Integer> start(
            @RequestParam(required = true,name = "strategyName") String strategyName,
            @RequestParam(required = true,name = "version") String version,
            @RequestParam(required = true,name = "name") String name,
            @RequestParam(required = true,name = "beginDate") long beginDate,
            @RequestParam(required = true,name = "paramstr") String paramstr,
            @RequestParam(required = true,name = "initMoney") double initMoney) {
        UserDTO userDTO = appContext.get();
        return strategyTestService.start(strategyName,version,name,userDTO.name,beginDate,paramstr,initMoney);
    }

    @RequestMapping(value = "/{id}/updateDaily", method = RequestMethod.POST)
    @ResponseBody
    public ResultDTO<Void> updateDaily(@PathVariable(required = true) int id,
                                       @RequestParam(required = true,name = "thedate") long thedate,
                                       @RequestParam(required = true,name = "balance") double balance,
                                       @RequestParam(required = true,name = "equity") double equity,
                                       @RequestParam(required = true,name = "open") double open,
                                       @RequestParam(required = true,name = "close") double close,
                                       @RequestParam(required = true,name = "high") double high,
                                       @RequestParam(required = true,name = "low") double low) {
        return strategyTestService.updateDaily(id,thedate,balance,equity,open,close,high,low);
    }

    @RequestMapping(value = "/{id}/end", method = RequestMethod.POST)
    @ResponseBody
    public ResultDTO<Void> end(@PathVariable(required = true) int id,
                               @RequestParam(required = true,name = "thedate") long thedate,
                               @RequestParam(required = true,name = "equity") double equity) {
        return strategyTestService.end(id,thedate,equity);
    }

    @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
    @ResponseBody
    public ResultDTO<Void> delete(@PathVariable(required = true) int id) {
        return strategyTestService.delete(id);
    }

    @RequestMapping(value = "", method = RequestMethod.DELETE)
    @ResponseBody
    public ResultDTO<Void> batchDelete(@RequestParam(required = true) String ids) {
        String[] idArr = ids.split("_");
        for(String id_str : idArr){
            strategyTestService.delete(Integer.parseInt(id_str));
        }
        return ResultDTO.success();
    }

    @RequestMapping(value = "/{id}/addOpen", method = RequestMethod.POST)
    @ResponseBody
    public ResultDTO<Void> addOpen(@PathVariable(required = true) int id,
                                   @RequestParam(required = true,name = "thedate") long thedate,
                                   @RequestParam(required = true,name = "symbol") String symbol,
                                   @RequestParam(required = true,name = "openPrice") double openPrice,
                                   @RequestParam(required = true,name = "amount") double amount,
                                   @RequestParam(required = true,name = "action") String action,
                                   @RequestParam(required = false,name = "sl") Double sl,
                                   @RequestParam(required = false,name = "tp") Double tp) {
        return strategyTestService.addOpen(id,thedate,symbol,openPrice,amount,action,sl,tp);
    }

    @RequestMapping(value = "/{id}/addClose", method = RequestMethod.POST)
    @ResponseBody
    public ResultDTO<Void> addClose(@PathVariable(required = true) int id,
                                    @RequestParam(required = true,name = "thedate") long thedate,
                                    @RequestParam(required = true,name = "symbol") String symbol,
                                    @RequestParam(required = true,name = "avgPrice") double avgPrice,
                                    @RequestParam(required = true,name = "closePrice") double closePrice,
                                    @RequestParam(required = true,name = "amount") double amount,
                                    @RequestParam(required = true,name = "action") String action,
                                    @RequestParam(required = true,name = "profit") double profit,
                                    @RequestParam(required = false,name = "commission") Double commission) {
        return strategyTestService.addClose(id,thedate,symbol,avgPrice,closePrice,amount,action,profit,commission);
    }

    @RequestMapping(value = "/{id}/addStatistics", method = RequestMethod.POST)
    @ResponseBody
    public ResultDTO<Void> addStatistics(@PathVariable(required = true) int id,
                                         @RequestParam(required = true,name = "beginTime") long beginTime,
                                         @RequestParam(required = true,name = "endTime") long endTime,
                                         @RequestParam(required = true,name = "beginMoney") double beginMoney,
                                         @RequestParam(required = true,name = "endMoney") double endMoney,
                                         @RequestParam(required = true,name = "tradeLevel") int tradeLevel,
                                         @RequestParam(required = true,name = "profit") double profit,
                                         @RequestParam(required = true,name = "amount") double amount,
                                         @RequestParam(required = true,name = "distance") int distance) {
        return strategyTestService.addStatistics(id,beginTime,endTime,beginMoney,endMoney,tradeLevel,profit,amount,distance);
    }


    private static StrategyTestDTO converDoToDto(StrategyTest d,boolean hasParameter) {
        StrategyTestDTO dto = new StrategyTestDTO();
        BeanTools.copyPublicFields(d, dto);
        if(hasParameter){
            StrategyParameter.format(dto.parameterList,d.paramstr);
        }
        return dto;
    }

    private static List<StrategyTestDTO> converDoToDtos(List<StrategyTest> list) {
        return list.stream().map(x -> converDoToDto(x,false)).collect(Collectors.toList());
    }

    private static Page<StrategyTestDTO> converDoToDtoPage(Page<StrategyTest> page){
        return new PageImpl(converDoToDtos(page.getContent()),page.getPageable(),page.getTotalElements());
    }
}
